/*
	Copyright 2011 Dario Seitel, Sebastian Mayer. All rights reserved.

	Redistribution and use in source and binary forms, with or without modification, are
	permitted provided that the following conditions are met:

	   1. Redistributions of source code must retain the above copyright notice, this list of
		  conditions and the following disclaimer.

	   2. Redistributions in binary form must reproduce the above copyright notice, this list
		  of conditions and the following disclaimer in the documentation and/or other materials
		  provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR IMPLIED
	WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
	ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	The views and conclusions contained in the software and documentation are those of the
	authors and should not be interpreted as representing official policies, either expressed
	or implied, of <copyright holder>.
*/

#define ENABLE_ARIBA

#include "GameEngine.h"

#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include "maps/MapGenerator.h"
#include "maps/TestMapGenerator.h"
#include "maps/Region.h"
#include "entities/Shot.h"
#include "entities/Floor.h"
#include "entities/Flag.h"
#include "../tools/Math.h"
#include "../output/Renderer.h"
#include "ariba/utility/system/StartupWrapper.h"
#include <iostream>
#include <sstream>


#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/list.hpp>

using ariba::utility::StartupWrapper;

string GameEngine::confPath;

GameEngine::GameEngine() : gameRunning(false), mapWidth(2), mapHeight(1), managedRegionX(0), managedRegionY(0) {
	#ifdef ENABLE_ARIBA
		StartupWrapper::initConfig(GameEngine::confPath);
		StartupWrapper::startSystem();
		aribaNode = new AribaNode();
		StartupWrapper::startup(aribaNode, false);
	#endif
}

GameEngine::~GameEngine() {
	// free map memory
	for (int x = 0; x < getMapWidth(); x++) {
		for (int y = 0; y < getMapHeight(); y++) {
			delete regions[x][y];
		}
		delete[] regions[x];
	}

	delete[] regions;

	// delete entities
	for (list<Entity*>::iterator it = renderedObjects.begin(); it != renderedObjects.end(); it++) {
		delete *it;
		renderedObjects.erase(it++);
	}

	for (list<Shot*>::iterator it = shots.begin(); it != shots.end(); it++) {
		delete *it;
		shots.erase(it++);
	}

	for (list<Player*>::iterator it = players.begin(); it != players.end(); it++) {
		delete *it;
		players.erase(it++);
	}

	for (list<Collectible*>::iterator it = collectibles.begin(); it != collectibles.end(); it++) {
		delete *it;
		collectibles.erase(it++);
	}

	delete mapGenerator;
}

GameEngine &GameEngine::get() {
	static GameEngine instance;
	return instance;
}

void GameEngine::startGame() {
	// TODO remove, just for debugging
	Configuration& config = Configuration::instance();
	bool isPlayer2 = false;
	if (config.exists("node.name") && config.read<string>("node.name") != "Client1")
		isPlayer2 = true;

	if (isPlayer2)
		mapGenerator = new TestMapGenerator();
	else
		mapGenerator = new MapGenerator(93);

	regions = new Region**[getMapWidth()];
	for (int x = 0; x < getMapWidth(); x++)
		regions[x] = new Region*[getMapHeight()];

	for (int x = 0; x < getMapWidth(); x++) {
		for (int y = 0; y < getMapHeight(); y++) {
			regions[x][y] = new Region(x, y);
		}
	}

/*
	player.setY(1 * MAP_TILE_SIZE + 50);
	player.setX(2 * MAP_TILE_SIZE + 20);
	addObject(&player);
*/
	/*
	Player* testOpponent = new Player();
	testOpponent->setY(1 * MAP_TILE_SIZE + 20);
	testOpponent->setX(1 * MAP_TILE_SIZE + 20);
	addObject(testOpponent);
	*/

	gameRunning = true;
}

void GameEngine::run() {
	sf::RenderWindow App(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32), "Dynamic Multiplayer Game");
	const sf::Input& Input = App.GetInput();
	App.SetFramerateLimit(60); // Limit to 60 frames per second

	Renderer renderer;

	while (App.IsOpened())
	{
		if (!gameRunning)
			continue;

		// handle events
		sf::Event Event;
		while (App.GetEvent(Event))
		{
			// Window closed
			if (Event.Type == sf::Event::Closed)
				App.Close();

			// Escape key pressed
			if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) {
				StartupWrapper::shutdown(aribaNode);
				StartupWrapper::stopSystem();
				App.Close();
				return;
			}

			if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::P)){
				aribaNode->publish();
			}

			if ((Event.Type == sf::Event::KeyReleased) && (Event.Key.Code == sf::Key::Up)){
				struct Player::MOVE_TRANSLATION moveTranslation;
				moveTranslation.translation = Player::NONE;
				player.setMoveTranslation(moveTranslation);

				aribaNode->stopUpDownKeyMessage(1);
			}

			if ((Event.Type == sf::Event::KeyReleased) && (Event.Key.Code == sf::Key::Down)){
				struct Player::MOVE_TRANSLATION moveTranslation;
				moveTranslation.translation = Player::NONE;
				player.setMoveTranslation(moveTranslation);

				aribaNode->stopUpDownKeyMessage(0);
			}

			if ((Event.Type == sf::Event::KeyReleased) && (Event.Key.Code == sf::Key::Right)){
				struct Player::MOVE_ROTATION moveRotation;
				moveRotation.rotation = Player::NONE;
				player.setMoveRotation(moveRotation);

				aribaNode->stopRotationMessage(0);
			}

			if ((Event.Type == sf::Event::KeyReleased) && (Event.Key.Code == sf::Key::Left)){
				struct Player::MOVE_ROTATION moveRotation;
				moveRotation.rotation = Player::NONE;
				player.setMoveRotation(moveRotation);

				aribaNode->stopRotationMessage(1);
			}

			// toggle debug
			if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::D))
				enableDebug = !enableDebug;

			// debug key bindings
			if (enableDebug && Event.Type == sf::Event::KeyPressed) {
				if (Event.Key.Code == sf::Key::W) {
					toggleDebugFeature(DEBUG_FEATURE_WIREFRAME);
					if (isEnabledDebugFeature(DEBUG_FEATURE_WIREFRAME))
						glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
					else
						glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
				}

				if (Event.Key.Code == sf::Key::C) {
					toggleDebugFeature(DEBUG_FEATURE_PLAYER_TERRAIN_NOCLIP);
					player.toggleProperty(PROP_COLLISION_TERRAIN);
				}

				if (Event.Key.Code == sf::Key::B) {
					toggleDebugFeature(DEBUG_FEATURE_COLLISION_BOXES);
				}

				if (Event.Key.Code == sf::Key::R) {
					toggleDebugFeature(DEBUG_FEATURE_REGION_BORDERS);
				}
			}
		}

		// directional inputs
		if (Input.IsKeyDown(sf::Key::Up)) {

			struct Player::MOVE move;
			move.delay = 0;
			struct Player::MOVE_TRANSLATION moveTranslation;
			moveTranslation.move = move;
			moveTranslation.translation = Player::UP;
			player.setMoveTranslation(moveTranslation);

			aribaNode->sendUpDownKeyMessage(1);
		}


		if (Input.IsKeyDown(sf::Key::Down)) {
			struct Player::MOVE move;
			move.delay = 0;
			struct Player::MOVE_TRANSLATION moveTranslation;
			moveTranslation.move = move;
			moveTranslation.translation = Player::DOWN;
			player.setMoveTranslation(moveTranslation);

			aribaNode->sendUpDownKeyMessage(0);
		}

		if (Input.IsKeyDown(sf::Key::Left))
		{
			struct Player::MOVE move;
			move.delay = 0;
			struct Player::MOVE_ROTATION moveRotation;
			moveRotation.move = move;
			moveRotation.rotation = Player::LEFT;
			player.setMoveRotation(moveRotation);

			aribaNode->sendRotationMessage(1);
		}

		if (Input.IsKeyDown(sf::Key::Right))
		{
			struct Player::MOVE move;
			move.delay = 0;
			struct Player::MOVE_ROTATION moveRotation;
			moveRotation.move = move;
			moveRotation.rotation = Player::RIGHT;
			player.setMoveRotation(moveRotation);

			aribaNode->sendRotationMessage(0);
		}

		calcPlayerPositions(App.GetFrameTime());

		for (list<Entity*>::iterator it = renderedObjects.begin(); it != renderedObjects.end(); it++) {
			(*it)->tick();
		}

		// shooting
		if (Input.IsKeyDown(sf::Key::Space) && player.canShoot()) {
			Shot* shot = new Shot(player.getX(), player.getY() + player.getBoundingBoxHalfHeight());
			float x = cos(deg2rad(player.getRotation() + 90));// * player.getAccelerationX() * App.GetFrameTime());
			float y = sin(deg2rad(player.getRotation() + 90));// * player.getAccelerationX() * App.GetFrameTime());
			shot->setVelocityX(x + player.getVelocityX());
			shot->setVelocityY(y + player.getVelocityY());
			aribaNode->sendShot(shot);
			addObject(shot);
		}

		// shot collisions
		for (list<Shot*>::iterator it = shots.begin(); it != shots.end(); it++) {
			Shot* shot = *it;
			
			if(shot->getDistance() <= 0)
			{
				it++;
				removeObject(shot);
				delete shot;

			}else{
				float x = shot->getX() + shot->getVelocityX();
				float y = shot->getY() + shot->getVelocityY();
				Entity*** map = getMatchingRegionMap(x, y);
				if (checkCollisionAgainstTerrain(0, 1, **it)) {
					int regionTileX = xToRegionTileX(x);
					int regionTileY = yToRegionTileY(y);
					if (map[regionTileX][regionTileY]->hasProperty(PROP_DESTRUCTIBLE)) {
						delete map[regionTileX][regionTileY];
						map[regionTileX][regionTileY] = new Floor(xToTileX(x) * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, yToTileY(y) * MAP_TILE_SIZE + MAP_TILE_SIZE / 2);

						Flag* flag = new Flag(xToTileX(x) * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, yToTileY(y) * MAP_TILE_SIZE + MAP_TILE_SIZE / 2);
						addObject(flag);
					}
					it++;
					removeObject(shot);
					delete shot;
				}
			}
		}

		// joining/leaving neighbor regions
		manageGroupMemberships();

		// render output
		renderer.run();

		// render debug info
		if (enableDebug) {
			// TODO apparently this is expensive, might want to find another solution
			App.PreserveOpenGLStates(true);

			float Framerate = 1.f / App.GetFrameTime();
			static float smoothenedFPS = 60;
			smoothenedFPS = smoothenedFPS * 0.99f + Framerate * 0.01f;
			ostringstream stringStream;
			stringStream << "FPS: " << (int)smoothenedFPS << "\n"
					<< "w (" << std::boolalpha << isEnabledDebugFeature(DEBUG_FEATURE_WIREFRAME) << ") - wireframe mode" << "\n"
					<< "c (" << std::boolalpha << isEnabledDebugFeature(DEBUG_FEATURE_PLAYER_TERRAIN_NOCLIP) << ") - disable player/terrain collision" << "\n"
					<< "b (" << std::boolalpha << isEnabledDebugFeature(DEBUG_FEATURE_COLLISION_BOXES) << ") - show collision boxes" << "\n"
					<< "r (" << std::boolalpha << isEnabledDebugFeature(DEBUG_FEATURE_REGION_BORDERS) << ") - show region borders" << "\n"
					<< "p - show playerlist \n"
					<< "isHost: "<< aribaNode->isHost<<"\n"
					<< "isInitator: "<< aribaNode->isInitiator<<"\n";

			sf::String Text(stringStream.str(), sf::Font::GetDefaultFont(), 14);
			App.Draw(Text);

			if(Event.Key.Code == sf::Key::Q) {
				ostringstream stringStream;
				stringStream << "Playerlist:"<<"\n";

				for (list<Player*>::iterator it = players.begin(); it != players.end(); it++) {
					Player* player = *it;
					stringStream << player->getName()<<"\n";
				}

				sf::String PlayerList(stringStream.str(), sf::Font::GetDefaultFont(), 14);
				PlayerList.SetPosition(200,100);
				App.Draw(PlayerList);
			}

		}

		App.Display();
	}
}

void GameEngine::calcPlayerPositions(float frameTime){
	for(list<Player*>::iterator it = getPlayers().begin(); it != getPlayers().end(); it++) {
		Player* player = *it;

		if (player->getMoveTranslation().translation == Player::UP)
		{
			// FIXME shouldn't this be dependent on the framerate as well?
			float x = cos(deg2rad(player->getRotation() + 90));
			float y = sin(deg2rad(player->getRotation() + 90));
			player->changeVelocityX(x * 0.3);
			player->changeVelocityY(y * 0.3);
		}

		if (player->getMoveTranslation().translation == Player::DOWN)
		{
			// FIXME shouldn't this be dependent on the framerate as well?
			float x = cos(deg2rad(player->getRotation() + 90));
			float y = sin(deg2rad(player->getRotation() + 90));
			player->changeVelocityX(- x * 0.3);
			player->changeVelocityY(- y * 0.3);
		}

		// FIXME shouldn't this be dependent on the framerate as well?
		player->setVelocityX(player->getVelocityX() * 0.9f);
		player->setVelocityY(player->getVelocityY() * 0.9f);

		if (player->getMoveRotation().rotation == Player::LEFT)
		{
			player->changeRotation(player->getRotationSpeed() * frameTime);
		}

		if (player->getMoveRotation().rotation == Player::RIGHT)
		{
			player->changeRotation(-player->getRotationSpeed() * frameTime);
		}

		if (player->getVelocityX() || player->getVelocityY()) {
			float dx = player->getVelocityX();
			float dy = player->getVelocityY();
			float newX = player->getX() + dx;
			float newY = player->getY() + dy;

			player->setX(newX);
			if (checkCollisionAgainstTerrain(dx, 0, *player)) {
				int tileX = xToTileX(newX - dx);
				if (dx > 0)
					player->setX((tileX + 1) * MAP_TILE_SIZE - player->getBoundingBoxHalfHeight() - 0.01f);
				else
					player->setX(tileX * MAP_TILE_SIZE + player->getBoundingBoxHalfHeight() + 0.01f);
				player->setVelocityX(player->getVelocityX() * 0.9);
			}

			player->setY(newY);
			if (checkCollisionAgainstTerrain(0, dy, *player)) {
				int tileY = yToTileY(newY - dy);
				if (dy > 0)
					player->setY((tileY + 1) * MAP_TILE_SIZE - player->getBoundingBoxHalfHeight() - 0.01f);
				else
					player->setY(tileY * MAP_TILE_SIZE + player->getBoundingBoxHalfHeight() + 0.01f);
				player->setVelocityY(player->getVelocityY() * 0.9);
			}
		}

		// collecting collectibles
		for (list<Collectible*>::iterator it = collectibles.begin(); it != collectibles.end(); it++) {
			if (checkCollisionAABB(**it, *player)) {
				Collectible* collectible = *it;
				it++;
				player->onCollect(collectible);
			}
		}
	}
}

void GameEngine::checkToBeamPlayer(Player *player, float x, float y) {
	//if difference more then
	float diff = sqrt(pow(player->getX() - x,2) + pow(player->getY() - y,2));
	if(diff > 0.5f){
		player->setX(x);
		player->setY(y);
	}
	cout<<"Position Own: "<<player->getX()<<" || "<<player->getY()<<" Received: "<<x<<"||"<<y<<"Diff: "<<diff<<"\n";
}

void GameEngine::checkToBeamPlayer(Player *player, float angle, float x, float y) {
	//Set rotation
	player->setRotation(angle);
	//check absolut position
	this->checkToBeamPlayer(player, x, y);
}

Player &GameEngine::getPlayer() {
	return player;
}

Entity*** GameEngine::getMatchingRegionMap(float x, float y) {
	return getRegionMap(x / MAP_TILE_SIZE / REGION_WIDTH, y / MAP_TILE_SIZE / REGION_HEIGHT);
}

Region* GameEngine::getRegion(int x, int y) {
	if (x >= 0 && y >= 0 && x < getMapWidth() && y < getMapHeight())
		return regions[x][y];
	else
		return NULL;
}

void GameEngine::replaceRegionMap(Entity*** map, int regionX, int regionY) {
	Entity*** regionMap = getRegion(regionX, regionY)->getMap();
	for (int x = 0; x < REGION_WIDTH; x++) {
		for (int y = 0; y < REGION_HEIGHT; y++) {
			delete regionMap[x][y];
			regionMap[x][y] =  map[x][y];
		}
	}
}

Entity*** GameEngine::getRegionMap(int x, int y) {
	if (x >= 0 && y >= 0 && x < getMapWidth() && y < getMapHeight())
		return regions[x][y]->getMap();
	else
		return NULL;
}

void GameEngine::addObject(Entity* object) {
	renderedObjects.push_back(object);
}

void GameEngine::removeObject(Entity* object) {
	renderedObjects.remove(object);
}

void GameEngine::addObject(Shot* object) {
	shots.push_back(object);
	addObject((Entity*)object);
}

void GameEngine::removeObject(Shot* object) {
	shots.remove(object);
	removeObject((Entity*)object);
}

list<Entity*> &GameEngine::getRenderedObjects() {
	return renderedObjects;
}

void GameEngine::addObject(Player* object) {
	players.push_back(object);
	addObject((Entity*)object);
}

void GameEngine::removeObject(Player* object) {
	players.remove(object);
	removeObject((Entity*)object);
}

void GameEngine::addObject(Collectible* object) {
	collectibles.push_back(object);
	addObject((Entity*)object);
}

void GameEngine::removeObject(Collectible* object) {
	collectibles.remove(object);
	removeObject((Entity*)object);
}

list<Player*> &GameEngine::getPlayers() {
	return players;
}

Player* GameEngine::findPlayer(string name) {
	for (list<Player*>::iterator it = players.begin(); it != players.end(); it++) {
		Player* player = *it;
		if(player->getName().compare(name) == 0)
			return player;
	}
	return NULL;
}

void GameEngine::addNewPlayer(string name, float posX, float posY){
	Player* testOpponent = findPlayer(name);
	if(testOpponent == NULL)
	{
		testOpponent = new Player();
		testOpponent->setName(name);
		testOpponent->setX(posX);
		testOpponent->setY(posY);
		addObject(testOpponent);
	}else
	{
		testOpponent->setX(posX);
		testOpponent->setY(posY);
	}

}

void GameEngine::calcSpwanPosition(float* posX, float* posY){
	//FIXME looking for free place to set new player
	*posX = (float)1.0f * MAP_TILE_SIZE + 20.0f;
	*posY = (float)1.0f * MAP_TILE_SIZE + 20.0f;
}

bool GameEngine::checkCollisionAgainstTerrain(float dx, float dy, Entity &entity) {
	// check bottom/top edges
	if (dy != 0) {
		short sign = (dy > 0) ? 1 : -1;
		float y = entity.getY() + dy + sign * entity.getBoundingBoxHalfHeight();
		int regionTileY = yToRegionTileY(y);

		if (regionTileY >= 0 && regionTileY < REGION_HEIGHT) {
			float x = entity.getX() - entity.getBoundingBoxHalfWidth();
			int regionTileX = xToRegionTileX(x);
			Entity*** map = getMatchingRegionMap(x, y);
			if (map == NULL) {
				cout<<"something's wrong"<<endl;
				return false;
			}
			if (regionTileX >= 0 && regionTileX < REGION_WIDTH && checkCollisionAABB(entity, *map[regionTileX][regionTileY]))
				return true;
			x = entity.getX() + entity.getBoundingBoxHalfWidth();
			map = getMatchingRegionMap(x, y);
			regionTileX = xToRegionTileX(x);
			if (regionTileX >= 0 && regionTileX < REGION_WIDTH && checkCollisionAABB(entity, *map[regionTileX][regionTileY]))
				return true;
		}
	}

	// check left/right edges
	if (dx != 0) {
		short sign = (dx > 0) ? 1 : -1;
		float x = entity.getX() + dx + sign * entity.getBoundingBoxHalfWidth();
		int regionTileX = xToRegionTileX(x);

		if (regionTileX >= 0 && regionTileX < REGION_WIDTH) {
			float y = entity.getY() - entity.getBoundingBoxHalfHeight();
			Entity*** map = getMatchingRegionMap(x, y);
			if (map == NULL) {
				cout<<"something's wrong"<<endl;
				return false;
			}
			int regionTileY = yToRegionTileY(y);
			if (regionTileY >= 0 && regionTileY < REGION_HEIGHT && checkCollisionAABB(entity, *map[regionTileX][regionTileY]))
				return true;
			y = entity.getY() + entity.getBoundingBoxHalfHeight();
			map = getMatchingRegionMap(x, y);
			regionTileY = yToRegionTileY(y);
			if (regionTileY >= 0 && regionTileY < REGION_HEIGHT && checkCollisionAABB(entity, *map[regionTileX][regionTileY]))
				return true;
		}
	}

	return false;
};

bool GameEngine::checkCollisionAABB(Entity &a, Entity &b) {
	// check if the objects can collide at all
	if (!a.hasProperty(b.getCollisionType()) && !b.hasProperty(a.getCollisionType())) return false;

	// check for overlapping bounding boxes
	if (a.getX() + a.getBoundingBoxHalfWidth() < b.getX() - b.getBoundingBoxHalfWidth()) return false;
	if (a.getX() - a.getBoundingBoxHalfWidth() > b.getX() + b.getBoundingBoxHalfWidth()) return false;
	if (a.getY() + a.getBoundingBoxHalfHeight() < b.getY() - b.getBoundingBoxHalfHeight()) return false;
	if (a.getY() - a.getBoundingBoxHalfHeight() > b.getY() + b.getBoundingBoxHalfHeight()) return false;

	return true;
};

void GameEngine::toggleDebugFeature(int debugFeature) {
	enabledDebugFeatures ^= debugFeature;
}

bool GameEngine::isEnabledDebugFeature(int debugFeature) {
	return (enabledDebugFeatures & debugFeature) > 0;
}

int GameEngine::tileXtoRegionTileX(int x) {
	return x % REGION_WIDTH;
}

int GameEngine::tileYtoRegionTileY(int y) {
	return y % REGION_HEIGHT;
}

int GameEngine::xToTileX(float x) {
	return x / MAP_TILE_SIZE;
}

int GameEngine::yToTileY(float y) {
	return y / MAP_TILE_SIZE;
}

int GameEngine::xToRegionTileX(float x) {
	return tileXtoRegionTileX(xToTileX(x));
}

int GameEngine::yToRegionTileY(float y) {
	return tileYtoRegionTileY(yToTileY(y));
}

int GameEngine::getMapWidth() {
	return mapWidth;
}

int GameEngine::getMapHeight() {
	return mapHeight;
}

void GameEngine::setMapWidth(int mapWidth) {
	this->mapWidth = mapWidth;
}

void GameEngine::setMapHeight(int mapHeight) {
	this->mapHeight = mapHeight;
}

AribaNode &GameEngine::getAribaNode() {
	return *aribaNode;
}

void GameEngine::manageRegion(int managedRegionX, int managedRegionY, bool isBackup) {
	this->managedRegionX = managedRegionX;
	this->managedRegionY = managedRegionY;
	this->isBackup = isBackup;
	aribaNode->JoinGroup(calcGroupID(managedRegionX, managedRegionY));
}

bool GameEngine::isManagingRegion(int regionX, int regionY) {
	return (regionX == managedRegionX && regionY == managedRegionY);
}

int GameEngine::getManagedRegionX() {
	return managedRegionX;
}

int GameEngine::getManagedRegionY() {
	return managedRegionY;
}

int GameEngine::getCurrentRegionX(){
	return (int)(player.getX() / (REGION_WIDTH * MAP_TILE_SIZE));
}

int GameEngine::getCurrentRegionY(){
	return (int)(player.getY() / (REGION_HEIGHT * MAP_TILE_SIZE));
}

bool GameEngine::getIsBackup() {
	return isBackup;
}

int GameEngine::calcGroupID(int x, int y){
	return (y * mapWidth + x) + 1;
}

IMapGenerator* GameEngine::getMapGenerator() {
	return mapGenerator;
}

void GameEngine::manageGroupMemberships() {
	// leave all regions that are too far away
	for (list<Region*>::iterator it = joinedRegions.begin(); it != joinedRegions.end(); it++) {
		Region* region = *it;
		if (region->isWithinLeavingDistance(&player)) {
			it++;
			leaveRegion(region);
		}
	}

	int regionX = player.getX() / (REGION_WIDTH * MAP_TILE_SIZE);
	int regionY = player.getY() / (REGION_HEIGHT * MAP_TILE_SIZE);
	// join neighboring regions
	for (int x = regionX - 1; x <= regionX + 1; x++) {
		if (x < 0 || x >= getMapWidth())
			continue;

		for (int y = regionY - 1; y <= regionY + 1; y++) {
			if (y < 0 || y >= getMapHeight())
				continue;

			Region* region = getRegion(x, y);
			if (!isRegionJoined(region) && region->isWithinJoiningDistance(&player))
				joinRegion(region);
		}
	}
}

bool GameEngine::isRegionJoined(Region* region) {
	return find(joinedRegions.begin(), joinedRegions.end(), region) != joinedRegions.end();
}

void GameEngine::joinRegion(Region* region) {
	if (aribaNode->isReady()) {
		joinedRegions.push_back(region);
		aribaNode->connectToRegion(calcGroupID(region->getX(), region->getY()));
	}
}

void GameEngine::leaveRegion(Region* region) {
	joinedRegions.remove(region);
	if (!isManagingRegion(region->getX(), region->getY()))
		aribaNode->LeaveGroup(calcGroupID(region->getX(), region->getY()));
}

list<Region*> GameEngine::getJoinedRegions() {
	return joinedRegions;
}
